package com.sinog.core.util;

import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.QuadCurve2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Objects;

/**
 * @desc 验证码实体类
 * @author lj
 * @date -
 */
@Slf4j
public class Captchas {

    /**
     * 随机数
     */
    private static final SecureRandom RANDOM = new SecureRandom();
    /**
     * 验证码字符
     */
    private static final char[] ALPHA = {'2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z'};
    /**
     * 颜色组合
     */
    private static final int[][] COLOR = {{0,135,255},{51,153,51},{255,102,102},{255,153,0},{153,102,0},{153,102,153},{51,153,153},{102,102,255},{0,102,204},{204,51,51},{0,153,204},{0,51,102}};
    /**
     * 使用字体
     */
    private static final int FONT_1 = 0;
    /**
     * 字体集合
     */
    private static final String[] FONT_NAMES = new String[]{"actionj.ttf","epilog.ttf","fresnel.ttf","headache.ttf","lexo.ttf","prefix.ttf","progbot.ttf","ransom.ttf","robot.ttf","scandal.ttf"};
    /**
     * 验证码使用字体
     */
    private Font font = null;
    /**
     * 验证码字符长度
     */
    private static int len = 4;
    /**
     * 验证码显示宽度
     */
    private static int width = 150;
    /**
     * 验证码显示高度
     */
    public static int height = 50;
    /**
     * 当前验证码
     */
    private String chars = null;
    private static final Integer TWOFF = 255;
    //===============================随机数===========================================

    /**
     * 获取0 -- num 之间的随机数
     * @param num 最大数
     * @return int
     */
    public int getRandom(int num) {
        return RANDOM.nextInt(num);
    }

    /**
     * 获取min -- max 之间的随机数
     * @param min 最小数
     * @param max 最大数
     * @return int
     */
    public int getRandom(int min,int max) {
        return min + RANDOM.nextInt(max - min);
    }

    /**
     * 返回字符集中随机字符
     * @return char
     */
    public char getAlPha() {
        return ALPHA[getRandom(ALPHA.length)];
    }

    /**
     * 生成默认长度的字符数组
     */
    public void getAlPhas() {
        char[] cs = new char[len];
        for(int i = 0; i < len; i++) {
            cs[i] = getAlPha();
        }
        chars = new String(cs);
        log.error(chars);
    }
    //===============================验证码属性配置===========================================

    /**
     * 设置给定范围内的颜色
     * @param fc fc
     * @param bc bc
     * @return Color
     */
    public Color color(int fc,int bc) {
        if(fc > TWOFF) {
            fc = 255;
        }
        if(bc > TWOFF) {
            bc = 255;
        }
        int r = fc + getRandom(bc - fc);
        int g = fc + getRandom(bc - fc);
        int b = fc + getRandom(bc - fc);
        return new Color(r,g,b);
    }

    /**
     * 获取给定的随机颜色
     * @return Color
     */
    public Color color() {
        int[] color = COLOR[getRandom(COLOR.length)];
        return new Color(color[0],color[1],color[2]);
    }

    /**
     * 随机画干扰线
     * @param num 数量
     * @param g Graphics2D
     */
    public void drawLine(int num,Graphics2D g) {
        drawLine(num,null,g);
    }

    /**
     * 随机画干扰线
     * @param num 数量
     * @param color 颜色
     * @param g Graphics2D
     */
    public void drawLine(int num,Color color,Graphics2D g) {
        for(int i = 0; i < num; i++) {
            g.setColor(null == color ? color() : color);
            int x1 = getRandom(-10,width - 10);
            int y1 = getRandom(5,height - 5);
            int x2 = getRandom(10,width + 10);
            int y2 = getRandom(2,height - 2);
            g.drawLine(x1,y1,x2,y2);
        }
    }

    /**
     * 随机画干扰圆
     * @param num 数量
     * @param g Graphics2D
     */
    public void drawOval(int num,Graphics2D g) {
        drawOval(num,null,g);
    }

    /**
     * 随机画干扰圆
     * @param num 数量
     * @param color 颜色
     * @param g Graphics2D
     */
    public void drawOval(int num,Color color,Graphics2D g) {
        for(int i = 0; i < num; i++) {
            g.setColor(null == color ? color() : color);
            int w = 5 + getRandom(10);
            g.drawOval(getRandom(width - 25),getRandom(height - 15),w,w);
        }
    }

    /**
     * 随机画贝塞尔曲线
     * @param num 数量
     * @param g Graphics2D
     */
    public void drawBesselLine(int num,Graphics2D g) {
        drawBesselLine(num,null,g);
    }

    /**
     * 随机画贝塞尔曲线
     * @param num 数量
     * @param color 颜色
     * @param g Graphics2D
     */
    public void drawBesselLine(int num,Color color,Graphics2D g) {
        for(int i = 0; i < num; i++) {
            g.setColor(null == color ? color() : color);
            int x1 = 5, y1 = getRandom(5,height / 2);
            int x2 = width - 5, y2 = getRandom(height / 2,height - 5);
            int ctrlx = getRandom(width / 4,width / 4 * 3), ctrly = getRandom(5,height - 5);
            if(getRandom(2) == 0) {
                int ty = y1;
                y1 = y2;
                y2 = ty;
            }
            // 二阶贝塞尔曲线
            if(getRandom(2) == 0) {
                QuadCurve2D shape = new QuadCurve2D.Double();
                shape.setCurve(x1,y1,ctrlx,ctrly,x2,y2);
                g.draw(shape);
            } else {
                // 三阶贝塞尔曲线
                int ctrlx1 = getRandom(width / 4,width / 4 * 3), ctrly1 = getRandom(5,height - 5);
                CubicCurve2D shape = new CubicCurve2D.Double(x1,y1,ctrlx,ctrly,ctrlx1,ctrly1,x2,y2);
                g.draw(shape);
            }
        }
    }
    //===============================字体配置===========================================

    /**
     * 字体属性
     * @return Font
     */
    public Font getFont() {
        if(null == font) {
            try {
                setFont(FONT_1);
            } catch(Exception e) {
                setFont(new Font("Arial",Font.BOLD,32));
            }
        }
        return font;
    }

    public void setFont(Font font) {
        this.font = font;
    }

    public void setFont(int font) throws IOException, FontFormatException {
        setFont(font,32f);
    }

    public void setFont(int font,float size) throws IOException, FontFormatException {
        setFont(font,Font.BOLD,size);
    }

    public void setFont(int font,int style,float size) throws IOException, FontFormatException {
        this.font = Font.createFont(Font.TRUETYPE_FONT,Objects.requireNonNull(getClass().getResourceAsStream("/" + FONT_NAMES[font])))
                        .deriveFont(style,size);
    }

    public static int getLen() {
        return len;
    }

    public static void setLen(int len) {
        Captchas.len = len;
    }

    public static int getWidth() {
        return width;
    }

    public static void setWidth(int width) {
        Captchas.width = width;
    }

    public static int getHeight() {
        return height;
    }

    public static void setHeight(int height) {
        Captchas.height = height;
    }
    //===============================验证码生成===========================================

    /**
     * 获取当前验证码
     * @return String
     */
    public String text() {
        checkAlpha();
        return chars;
    }

    /**
     * 获取当前验证码的字符数组
     * @return char[]
     */
    public char[] textChar() {
        checkAlpha();
        return chars.toCharArray();
    }

    /**
     * 校验验证码是否生成，如果没生成，则生成
     */
    public void checkAlpha() {
        if(null == chars) {
            getAlPhas();
        }
    }
    //===============================验证码图片生成===========================================

    /**
     * 描述
     * @param os os
     */
    public void out(OutputStream os) {
        graphicsImage(textChar(),os);
    }

    /**
     * 描述
     * @param type type
     * @return String
     */
    public String toBase64(String type) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        out(outputStream);
        return type + Base64.getEncoder().encodeToString(outputStream.toByteArray());
    }

    /**
     * 验证码详细输出
     * @param strs 验证码
     * @param out out
     */
    public void graphicsImage(char[] strs,OutputStream out) {
        try {
            BufferedImage bi = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
            Graphics2D g2d = (Graphics2D)bi.getGraphics();
            // 填充背景
            g2d.setBackground(Color.WHITE);
            g2d.fillRect(0,0,width,height);
            // 抗锯齿
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
            // 画干扰图
            drawOval(2,g2d);
            // 画干扰线
            g2d.setStroke(new BasicStroke(2f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
            drawBesselLine(1,g2d);
            // 画字符串
            g2d.setFont(getFont());
            FontMetrics fontMetrics = g2d.getFontMetrics();
            //每一个字符所占的宽度
            int fW = width / strs.length;
            //字符的左右边距
            int fsp = (fW - (int)fontMetrics.getStringBounds("W",g2d).getWidth()) / 2;
            for(int i = 0; i < strs.length; i++) {
                g2d.setColor(color());
                int fY = height - ((height - (int)fontMetrics.getStringBounds(String.valueOf(strs[i]),g2d)
                                                             .getHeight()) >> 1);
                g2d.drawString(String.valueOf(strs[i]),i * fW + fsp + 3,fY - 3);
            }
            g2d.dispose();
            ImageIO.write(bi,"png",out);
            out.flush();
        } catch(IOException e) {
            log.error(e.getMessage());
        } finally {
            try {
                out.close();
            } catch(Exception e) {
                log.error(e.getMessage());
            }
        }
    }
}